/*
 * Implementacion del Analizador Lexico para el lenguaje C Minus.
 * 
 * Basado en el proyecto descrito en: 
 * Louden, K. (1997). Compiler Construction: Principles and Practice. Boston: PWS.
 * 
 * @ Project: Compiler
 * @ File Name: Scanner.java
 * @ Date: 21/09/2008
 */
package cml.scanning;

import java.io.IOException;
import java.io.InputStream;

import cml.Lexicon;
import cml.TokenType;
import cml.scanning.automata.Automaton;
import cml.scanning.automata.CommentAutomaton;
import cml.scanning.automata.DecAutomaton;
import cml.scanning.automata.EqAutomaton;
import cml.scanning.automata.GTEqAutomaton;
import cml.scanning.automata.IdAutomaton;
import cml.scanning.automata.LTEqAutomaton;
import cml.scanning.automata.NEqAutomaton;
import cml.scanning.automata.OperatorAutomaton;
import cml.scanning.automata.TextAutomaton;

public class Scanner{
	
	private SourceReader reader;
	private String tokenString;
	private boolean trace;	
	
	/*
	 * Constructor de la clase.
	 * 
	 * @param	fileName	nombre del archivo con el codigo fuente
	 */
	public Scanner(String source){
		
		try{
			
			reader = new SourceReader(source);
		} 
		catch(IOException e){
			
			e.printStackTrace();
		}
	}
	
	/*
	 * Constructor de la clase.
	 * 
	 * @param	in	nombre del flujo de datos con el codigo fuente
	 */
	public Scanner(final InputStream in) throws IOException {

		reader = new SourceReader(in);
	}
	
	/*
	 * Devuelve la linea actual leida por el SourceReader
	 * 
	 * @return	<code>linea</code> actual
	 */
	public int getCurrentLine(){

		return this.reader.getCurrentLine();
	}
	
	/*
	 * Devuelve el siguiente <code>token</code> encontrado.
	 * 
	 * @return	<code>token</code> encontrado
	 */
	public TokenType getToken(){
		
		TokenType currentToken = null;
		this.tokenString = "";
		boolean done = false;
		
		Automaton automata[] = new Automaton[9];
		automata[0] = new CommentAutomaton();
		automata[1] = new TextAutomaton();
		automata[2] = new DecAutomaton();
		automata[3] = new IdAutomaton();		
		automata[4] = new NEqAutomaton();
		automata[5] = new GTEqAutomaton();
		automata[6] = new LTEqAutomaton();
		automata[7] = new EqAutomaton();
		automata[8] = new OperatorAutomaton();		
		
		int currentAt = -1;
		char c;
		
		while((c = reader.getNextChar()) != SourceReader.EOF){
			
			if(currentAt != -1 && automata[currentAt].nextStep(c)){
				if(automata[currentAt].isDone()){

					done = true;
				}
			}			

			for(int i = 0; i < automata.length && currentAt == -1; i++){

				if(automata[i].nextStep(c)){

					currentAt = i;

					if(automata[i].isDone()){

						done = true;
						break;
					}
				}
			}
			
			if(done){
				
				currentToken = automata[currentAt].getToken();
				this.tokenString = automata[currentAt].getTokenString();
				if(automata[currentAt].backtrack())
					reader.ungetNextChar();
				
				if(currentToken != null)
					break;
				else{ 
					currentAt = -1;
					done = false;
				}
			}
		}

		if(c == SourceReader.EOF)
			currentToken = TokenType.ENDFILE;
		
		if(currentToken == TokenType.ID)
			if((currentToken = this.isReservedWord(this.tokenString)) == TokenType.ID)
				currentToken = this.isLiteral(this.tokenString);
		
		if(trace)
			this.trace(currentToken, this.tokenString);
		
		return currentToken;
	}
	
	/*
	 * Devuelve el lexema del <code>token</code> mas reciente.
	 * 
	 * @return	lexema del <code>token</code>	
	 */
	public String getTokenString(){
		
		return this.tokenString;
	}
	
	/*
	 * Determina si el lexema corresponde a una palabra reservada.
	 * 
	 * @param	String	lexema del <code>token</code>
	 * @return			<code>token</code> correspondiente
	 */
	public TokenType isReservedWord(String tokenString) {
		
		TokenType token = Lexicon.isReserved(tokenString);
		
		if(token != null)
			return token;
		else return TokenType.ID;
	}
	
	/*
	 * Determina si el lexema corresponde es un valor literal.
	 * 
	 * @param	String	lexema del <code>token</code>
	 * @return			<code>token</code> correspondiente
	 */
	public TokenType isLiteral(String tokenString) {
		
		TokenType token = Lexicon.isLiteral(tokenString);
		
		if(token != null)
			return token;
		else return TokenType.ID;	
	}
	
	/*
	 * Habilita/deshabilita el mostrar en consola un reporte de los 
	 * <code>tokens</code> encontrados.
	 * 
	 * @param	boolean	si <code>true</code>, muestra el reporte; en caso
	 * 					contrario no lo hace
	 */
	public void setTrace(boolean t){
		
		this.trace = t;
	}
	
	/*
	 * Imprime el <code>token</code> y su lexema.
	 * 
	 * @param	TokenType	<code>token</code>
	 * @param	String		lexema
	 */
	private void trace(TokenType token, String tokenString){
		
		int ln = reader.getCurrentLine() + 1;
		
		System.out.print(ln + "| ");
		
		switch(token){
		
			case IF:
			case ELSE:
			case FOR:
			case WHILE:
			case RETURN:
			case VOID:
			case INT:
			case DEC:
			case TEXT:
			case BOOLEAN:
				System.out.println("Palabra reservada: " + tokenString);
				break;
			case TRUE:
			case FALSE:
				System.out.println("Literal booleana: " + tokenString);
				break;
			case PLUS:
			case MINUS:
			case TIMES:
			case OVER:
			case EQ:
			case NEQ:
			case LT:
			case LTEQ:
			case GT:
			case GTEQ:
			case ASSIGN:
			case COMMA:
			case SEMICOLON:
			case LPAREN:
			case RPAREN:
			case LBRAK:
			case RBRAK:
			case LBRACE:
			case RBRACE:
				System.out.println("Simbolo: " + tokenString);
				break;
			case NUMINT:
				System.out.println("Entero: " + tokenString);
				break;
			case NUMDEC:
				System.out.println("Decimal: " + tokenString);
				break;
			case ID:
				System.out.println("Identificador: " + tokenString);
				break;
			case TEXTLITERAL:
				System.out.println("Texto: " + tokenString);
				break;
			case ENDFILE:
				System.out.println("Fin del archivo");
				break;
			case ERROR:
				System.out.println("ERROR: " + tokenString);
				break;
			default:
				System.out.println("Token desconocido: " + tokenString);
		}
	}
}
